/*
 * Window.cpp
 *
 *	Author: stravant
 *	Description:
 *		
 */

#include "Window.h"

#include <iostream>

#include "../core/EventLoop.h"
#include "../ui/WidgetRoot.h"
#include "../ui/Canvas.h"

namespace tbt {



Window::Window(const WindowSettings& ws): mIsOpen(true),
	mGdkResizeCursor(0), mGdkCursorEdge(static_cast<GdkWindowEdge>(-1)), mResizeCursor(false) {
	//create window
	mWindow = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
	gtk_window_set_decorated(mWindow, ws.getDecorated());
	gtk_window_resize(mWindow, static_cast<gint>(ws.getWidth()), static_cast<gint>(ws.getHeight()));
	gtk_window_set_title(mWindow, ws.getTitle().c_str());
	gtk_widget_set_app_paintable(GTK_WIDGET(mWindow), true);
	//gtk_window_set_opacity(mWindow, 0.5);

	//setup signals
	gtk_widget_add_events(GTK_WIDGET(mWindow), GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
									GDK_SCROLL_MASK | GDK_POINTER_MOTION_MASK |
									GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
									GDK_STRUCTURE_MASK);
	auto windowObject = GTK_OBJECT(mWindow);
	gtk_signal_connect(windowObject, "delete-event", G_CALLBACK(Window::onRequestClose), this);
	gtk_signal_connect(windowObject, "button-press-event", G_CALLBACK(Window::onMouseDown), this);
	gtk_signal_connect(windowObject, "button-release-event", G_CALLBACK(Window::onMouseUp), this);
	gtk_signal_connect(windowObject, "enter-notify-event", G_CALLBACK(Window::onMouseEnter), this);
	gtk_signal_connect(windowObject, "leave-notify-event", G_CALLBACK(Window::onMouseLeave), this);
	gtk_signal_connect(windowObject, "motion-notify-event", G_CALLBACK(Window::onMouseMove), this);
	gtk_signal_connect(windowObject, "scroll-event", G_CALLBACK(Window::onMouseWheel), this);
	gtk_signal_connect(windowObject, "key-press-event", G_CALLBACK(Window::onKeyDown), this);
	gtk_signal_connect(windowObject, "key-release-event", G_CALLBACK(Window::onKeyUp), this);
	gtk_signal_connect(windowObject, "expose-event", G_CALLBACK(Window::onExposeNotify), this);
	gtk_signal_connect(windowObject, "configure-event", G_CALLBACK(Window::onConfigure), this);

	//internal vars
	mSize.set(ws.getWidth(), ws.getHeight());

	//colormap
	GdkScreen* screen = gtk_widget_get_screen(GTK_WIDGET(mWindow));
	GdkColormap* colorMap = gdk_screen_get_rgba_colormap(screen);
	assert(colorMap != nullptr && "Alpha Colormap not supported!");
	gtk_widget_set_colormap(GTK_WIDGET(mWindow), colorMap);

	//show it
	gtk_widget_show_all(GTK_WIDGET(mWindow));

	//get where it was displayed
	gint x, y;
	gtk_window_get_position(mWindow, &x, &y);
	mPosition.set(x, y);

	//make context
	mContext = new fx::Context(gdk_cairo_create(GTK_WIDGET(mWindow)->window));
}

Window::~Window() {
	delete mContext;
	gtk_widget_destroy(GTK_WIDGET(mWindow));
	gdk_cursor_destroy(mGdkResizeCursor);
}

fx::Context* Window::getContext() const {
	return mContext;
}

void Window::setPosition(int x, int y) {
	gtk_window_move(mWindow, static_cast<gint>(x), static_cast<gint>(y));
	mPosition.set(x, y);
}

void Window::setSize(int w, int h) {
	gtk_window_resize(mWindow, static_cast<gint>(w), static_cast<gint>(h));
	mSize.set(w, h);
}

void Window::setTitle(const String& s) {
	gtk_window_set_title(mWindow, s.c_str());
}

Ref<WidgetRoot> Window::getRootWidget() const {
	return mRootWidget;
}

void Window::setRootWidget(Ref<WidgetRoot> w) {
	if (mRootWidget) {
		mRootWidget->mWindow = nullptr;
	}
	mRootWidget = w;
	if (mRootWidget) {
		mRootWidget->mWindow = this;
		mRootWidget->setSize(mSize);
	}
}

void Window::beginWindowDrag(Mouse::Button bn, int rootx, int rooty) {
	int bnCode;
	switch (bn) {
	case Mouse::kButtonRight: bnCode = GDK_RIGHTBUTTON; break;
	case Mouse::kButtonLeft: bnCode = GDK_LEFTBUTTON; break;
	case Mouse::kButtonCenter: bnCode = GDK_MIDDLEBUTTON; break;
	}

	gtk_window_begin_move_drag(mWindow, bnCode, mPosition.x()+rootx, mPosition.y()+rooty, GDK_CURRENT_TIME);
}

//////// trampolines
gboolean Window::onRequestClose(GtkWidget* src, GdkEvent* drag, Window* data) {
	data->dispatchRequestClose();
	return TRUE;
}

gboolean Window::onConfigure(GtkWidget* src, GdkEvent* e, Window* data) {
	data->dispatchConfigureNotify(e);
	return FALSE;
}

gboolean Window::onExposeNotify(GtkWidget* src, GdkEvent* e, Window* data) {
	data->dispatchExposeNotify(e);
	return FALSE;
}

gboolean Window::onMouseDown(GtkWidget* src, GdkEvent* e, Window* data) {
	data->dispatchMouseDown(e);
	return FALSE;
}

gboolean Window::onMouseUp(GtkWidget* src, GdkEvent* e, Window* data) {
	data->dispatchMouseUp(e);
	return FALSE;
}

gboolean Window::onMouseEnter(GtkWidget* src, GdkEvent* e, Window* data) {
	data->dispatchMouseEnter(e);
	return FALSE;
}

gboolean Window::onMouseLeave(GtkWidget* src, GdkEvent* e, Window* data) {
	data->dispatchMouseLeave(e);
	return FALSE;
}

gboolean Window::onMouseWheel(GtkWidget* src, GdkEvent* e, Window* data) {
	data->dispatchMouseWheel(e);
	return FALSE;
}

gboolean Window::onMouseMove(GtkWidget* src, GdkEvent* e, Window* data) {
	data->dispatchMouseMove(e);
	return FALSE;
}

gboolean Window::onKeyDown(GtkWidget* src, GdkEvent* e, Window* data) {
	data->dispatchKeyDown(e);
	return FALSE;
}

gboolean Window::onKeyUp(GtkWidget* src, GdkEvent* e, Window* data) {
	data->dispatchKeyUp(e);
	return FALSE;
}


////////// dispatcher helpers
void Window::ActionDispatch_ButtonEvent::run() {mTarget->onEvent_Button(*this); }
Window::ActionDispatch_ButtonEvent::ActionDispatch_ButtonEvent(Ref<Widget> trg,
	Event::Type ty, Event::Flag flags, Mouse::Button which, const IVec& pos):
	MouseButtonEvent(ty, flags, which, pos), mTarget(trg) {}

void Window::ActionDispatch_MotionEvent::run() {mTarget->onEvent_Motion(*this); }
Window::ActionDispatch_MotionEvent::ActionDispatch_MotionEvent(Ref<Widget> trg,
	Event::Type ty, Event::Flag flags, const IVec& pos):
	MouseEvent(ty, flags, pos), mTarget(trg) {}

void Window::ActionDispatch_CrossingEvent::run() {mTarget->onEvent_Crossing(*this); }
Window::ActionDispatch_CrossingEvent::ActionDispatch_CrossingEvent(Ref<Widget> trg,
	Event::Type ty, Event::Flag flags, const IVec& pos):
	MouseEvent(ty, flags, pos), mTarget(trg) {}

void Window::ActionDispatch_WheelEvent::run() {mTarget->onEvent_Scroll(*this); }
Window::ActionDispatch_WheelEvent::ActionDispatch_WheelEvent(Ref<Widget> trg,
	Event::Type ty, Event::Flag flags, const IVec& pos, int delta):
	MouseWheelEvent(ty, flags, pos, delta), mTarget(trg) {}

void Window::ActionDispatch_KeyEvent::run() {mTarget->onEvent_Key(*this); }
Window::ActionDispatch_KeyEvent::ActionDispatch_KeyEvent(Ref<Widget> trg,
	Event::Type ty, Event::Flag flags, Key::Code kc):
	KeyEvent(ty, flags, kc), mTarget(trg) {}


///////// dispatchers
void Window::dispatchRequestClose() {
	//std::cout << "ERequestClose\n";
}

void Window::dispatchExposeNotify(GdkEvent* e) {
	class ActionInvokeRepaint: public Action {
	public:
		ActionInvokeRepaint(Ref<WidgetRoot> widget, const IRect& region):
			Widget(widget), Region(region) {}
		void run() {Widget->onRepaint(Region); }
	private:
		Ref<WidgetRoot> Widget;
		IRect Region;
	};

	GdkRectangle b;
	gdk_region_get_clipbox(e->expose.region, &b);
	if (mRootWidget)
		EventLoop::current()->queueAction(
				new ActionInvokeRepaint(mRootWidget, IRect(b.x, b.y, b.width, b.height)));
}

void Window::dispatchConfigureNotify(GdkEvent* e) {
	class ActionInvokeLayout: public Action {
	public:
		ActionInvokeLayout(Ref<WidgetRoot> widget, const IVec& sz): mWidget(widget), mNewSize(sz) {}
		void run() {mWidget->onResized(mNewSize); }
	private:
		Ref<WidgetRoot> mWidget;
		IVec mNewSize;
	};

	class ActionInvokeRepaint: public Action {
	public:
		ActionInvokeRepaint(Ref<WidgetRoot> widget, const IRect& region):
			Widget(widget), Region(region) {}
		void run() {Widget->onRepaint(Region); }
	private:
		Ref<WidgetRoot> Widget;
		IRect Region;
	};

	GdkEventConfigure* c = &(e->configure);
	mPosition.set(c->x, c->y);
	if (mSize != IVec(c->width, c->height)) {
		//resize

		//need a new cairo_context
		delete mContext;
		mContext = new fx::Context(gdk_cairo_create(GTK_WIDGET(mWindow)->window));

		//dispatch
		mSize.set(c->width, c->height);
		if (mRootWidget) {
			EventLoop::current()->queueAction(new ActionInvokeLayout(mRootWidget, mSize));
			EventLoop::current()->queueAction(
					new ActionInvokeRepaint(mRootWidget, IRect(IVec(), mSize)));
		}
//		std::cout << "Resized!\n";
	}
}

Mouse::Button ButtonFromGdkButtonCode(int code) {
	switch (code) {
	case 1: return Mouse::kButtonLeft;
	case 2: return Mouse::kButtonCenter;
	case 3: return Mouse::kButtonRight;
	default:
		unreachable;
	}
}

bool Window::getShouldResizeEdge(const IVec& pos, GdkWindowEdge& e) {
	static const int b = 6;
	if (pos.x() <= b || (mSize.x()-pos.x()) <= b || pos.y() <= b || (mSize.y()-pos.y()) <= b) {
		int xst = 0, yst = 0;
		//std::cout << "mSize: " << mSize << ", pos:" << pos << "\n";
		if (pos.x() <= b) {
			xst = -1;
		} else if ((mSize.x()-pos.x()) <= b) {
			xst = 1;
		}
		if (pos.y() <= b) {
			yst = -1;
		} else if ((mSize.y()-pos.y()) <= b) {
			yst = 1;
		}
		switch (xst) {
		case 1:
			//std::cout << "xst: Right\n";
			switch (yst) {
			case 1:	e = GDK_WINDOW_EDGE_SOUTH_EAST; break;
			case 0: e = GDK_WINDOW_EDGE_EAST; break;
			case -1:e = GDK_WINDOW_EDGE_NORTH_EAST; break;
			} break;
		case -1:
			//std::cout << "xst: Left\n";
			switch (yst) {
			case 1:	e = GDK_WINDOW_EDGE_SOUTH_WEST; break;
			case 0: e = GDK_WINDOW_EDGE_WEST; break;
			case -1:e = GDK_WINDOW_EDGE_NORTH_WEST; break;
			} break;
		case 0:
			//std::cout << "xst: Center\n";
			switch (yst) {
			case 1:	e = GDK_WINDOW_EDGE_SOUTH; break;
			case -1:e = GDK_WINDOW_EDGE_NORTH; break;
			} break;
		}
		return true;
	} else {
		return false;
	}
}

void Window::dispatchMouseDown(GdkEvent* e) {
	//resize event
	int x = e->button.x, y = e->button.y;
	GdkWindowEdge edge;
	if (e->button.button == 1 && getShouldResizeEdge(IVec(x, y), edge)) {
		//resize!
		std::cout << "Resize: " << edge << "\n";
		gtk_window_begin_resize_drag(mWindow, edge, 1, e->button.x_root, e->button.y_root, GDK_CURRENT_TIME);
	}

	Mouse::Button bn = ButtonFromGdkButtonCode(e->button.button);
	mInput.setButtonState(bn, true);

	//dispatch
	if (mRootWidget)
		EventLoop::current()->queueAction(new ActionDispatch_ButtonEvent(mRootWidget,
				Event::kEvtMouseDown, mInput.getCurrentFlags(), bn, IVec(e->button.x, e->button.y)));
}

void Window::dispatchMouseUp(GdkEvent* e) {
	Mouse::Button bn = ButtonFromGdkButtonCode(e->button.button);
	mInput.setButtonState(bn, false);

	//dispatch
	if (mRootWidget)
		EventLoop::current()->queueAction(new ActionDispatch_ButtonEvent(mRootWidget,
				Event::kEvtMouseUp, mInput.getCurrentFlags(), bn, IVec(e->button.x, e->button.y)));
}

void Window::dispatchMouseEnter(GdkEvent* e) {
	mInput.setMousePos(IVec(e->crossing.x, e->crossing.y));

	//dispatch
	if (mRootWidget)
		EventLoop::current()->queueAction(new ActionDispatch_CrossingEvent(mRootWidget,
				Event::kEvtMouseEnter, mInput.getCurrentFlags(), IVec(e->crossing.x, e->crossing.y)));
}

void Window::dispatchMouseLeave(GdkEvent* e) {
	//dispatch
	if (mRootWidget)
		EventLoop::current()->queueAction(new ActionDispatch_CrossingEvent(mRootWidget,
				Event::kEvtMouseLeave, mInput.getCurrentFlags(), IVec(e->crossing.x, e->crossing.y)));
}

int ScrollTicksFromScrollDir(GdkScrollDirection dir) {
	switch (dir) {
	case GDK_SCROLL_DOWN:	return 1;
	case GDK_SCROLL_UP:		return -1;
	case GDK_SCROLL_RIGHT:	return 0;
	case GDK_SCROLL_LEFT:	return 0;
	}
}

void Window::dispatchMouseWheel(GdkEvent* e) {
	auto scrollTicks = ScrollTicksFromScrollDir(e->scroll.direction);

	//dispatch
	if (mRootWidget && scrollTicks != 0)
		EventLoop::current()->queueAction(new ActionDispatch_WheelEvent(mRootWidget,
			Event::kEvtMouseWheel, mInput.getCurrentFlags(), IVec(e->scroll.x, e->scroll.y), scrollTicks));
}

void Window::dispatchKeyDown(GdkEvent* e) {
	auto keyCode = Key::FromGDKKeyCode(e->key.keyval);
	mInput.setKeyState(keyCode, true);

	//dispatch
	if (mRootWidget)
		EventLoop::current()->queueAction(new ActionDispatch_KeyEvent(mRootWidget,
			Event::kEvtKeyDown, mInput.getCurrentFlags(), keyCode));
}

void Window::dispatchKeyUp(GdkEvent* e) {
	auto keyCode = Key::FromGDKKeyCode(e->key.keyval);
	mInput.setKeyState(keyCode, false);

	//dispatch
	if (mRootWidget)
		EventLoop::current()->queueAction(new ActionDispatch_KeyEvent(mRootWidget,
			Event::kEvtKeyUp, mInput.getCurrentFlags(), keyCode));
}

void Window::dispatchMouseMove(GdkEvent* e) {
	mInput.setMousePos(IVec(e->motion.x, e->motion.y));

	//check for setting resize cursor
	GdkWindowEdge edge;
	if (getShouldResizeEdge(mInput.getMousePos(), edge)) {
		if (edge != mGdkCursorEdge) {
			mGdkCursorEdge = edge;
			gdk_cursor_destroy(mGdkResizeCursor);
			GdkCursorType ctype;
			switch (edge) {
			case GDK_WINDOW_EDGE_EAST:
				ctype = GDK_RIGHT_SIDE; break;
			case GDK_WINDOW_EDGE_NORTH_EAST:
				ctype = GDK_TOP_RIGHT_CORNER; break;
			case GDK_WINDOW_EDGE_NORTH:
				ctype = GDK_TOP_SIDE; break;
			case GDK_WINDOW_EDGE_NORTH_WEST:
				ctype = GDK_TOP_RIGHT_CORNER; break;
			case GDK_WINDOW_EDGE_WEST:
				ctype = GDK_LEFT_SIDE; break;
			case GDK_WINDOW_EDGE_SOUTH_WEST:
				ctype = GDK_BOTTOM_LEFT_CORNER; break;
			case GDK_WINDOW_EDGE_SOUTH:
				ctype = GDK_BOTTOM_SIDE; break;
			case GDK_WINDOW_EDGE_SOUTH_EAST:
				ctype = GDK_BOTTOM_RIGHT_CORNER; break;
			}
			mGdkResizeCursor = gdk_cursor_new(ctype);
		}
		gdk_window_set_cursor(GTK_WIDGET(mWindow)->window, mGdkResizeCursor);
	} else {
		gdk_window_set_cursor(GTK_WIDGET(mWindow)->window, nullptr);
	}

	//dispatch
	if (mRootWidget)
		EventLoop::current()->queueAction(new ActionDispatch_MotionEvent(mRootWidget,
			Event::kEvtMouseMove, mInput.getCurrentFlags(), IVec(e->motion.x, e->motion.y)));
}




}











